package org.ticlib.navigator
{
	import com.google.analytics.AnalyticsTracker;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	
	import mx.containers.ViewStack;
	import mx.core.Application;
	import mx.events.BrowserChangeEvent;
	import mx.events.FlexEvent;
	import mx.managers.IBrowserManager;
	
	[Event("change", type="flash.events.Event")]
	[DefaultProperty("descriptors")]
	public class Navigator extends EventDispatcher 
	{
		public static const PAGE_DESCRIPTOR:String = "page";
		public static const MODEL_DESCRIPTOR:String = "model";
		
		private var _roleManager:IUserManager;
		private var _browserManager:IBrowserManager;
		private var _urlManager:UrlManager;
		private var _container:ViewStack;
		private var _descriptors:Array;
		private var _redirectionStack:Array;
		private var _currentPage:String;
		private var _gaTracker:AnalyticsTracker;
		private var _selectedPageIndex:Number;
		private var _currentParams:Object;
		private var _isReload:Boolean;
				
		
		//--------------- Properties
		[Bindable("isReloadChange")]
		public function get isReload():Boolean
		{
			return _isReload;
		}
		
		public function set isReload(val:Boolean):void
		{
			_isReload = val;
			dispatchEvent(new Event("isReloadChange"));
		}
		
		[Bindable("currentParamsChange")]
		public function get currentParams():Object
		{
			return _currentParams;
		}
		
		public function set currentParams(val:Object):void
		{
			_currentParams = val;
			dispatchEvent(new Event("currentParamsChange"));
		}
		
		[Bindable("selectedPageIndexChange")]
		public function get selectedPageIndex():Number
		{
			return _selectedPageIndex;
		}
		
		public function set selectedPageIndex(val:Number):void
		{
			_selectedPageIndex = val;
			dispatchEvent(new Event("selectedPageIndexChange"));
		}
		
		[Bindable("roleManagerChange")]
		public function get roleManager():IUserManager
		{
			return _roleManager;
		}
		
		public function set roleManager(val:IUserManager):void
		{
			_roleManager = val;
			dispatchEvent(new Event("roleManagerChange"));
		}

		[Bindable("browserManagerChange")]
		public function get browserManager():IBrowserManager
		{
			return _browserManager;
		}
		
		public function set browserManager(val:IBrowserManager):void
		{
			_browserManager = val;
			initBrowserManager();
			dispatchEvent(new Event("browserManagerChange"));
		}
		
		[Bindable("containerChange")]
		public function get container():ViewStack
		{
			return _container;
		}
		
		public function set container(value:ViewStack):void
		{
			_container = value;
			dispatchEvent(new Event("containerChange"));
		}

		[Bindable("currentPageChange")]
		public function get currentPage():String
		{
			return _currentPage;
		}
		
		public function set currentPage(value:String):void
		{
			_currentPage = value;
			dispatchEvent(new Event("currentPageChange"));
		}
		
		[Bindable("gaTrackerChange")]
		public function get gaTracker():AnalyticsTracker
		{
			return _gaTracker;
		}
		
		public function set gaTracker(value:AnalyticsTracker):void
		{
			_gaTracker = value;
			dispatchEvent(new Event("gaTrackerChange"));
		}
		
		[ArrayElementType("org.ticlib.navigator.NavigationEntityDescriptor")]
		[Bindable("descriptorsChange")]
		public function get descriptors():Array
		{
			return _descriptors;
		}
		
		public function set descriptors(value:Array):void
		{
			var hasDefault:Boolean = false;
			for each(var page:NavigationEntityDescriptor in value)
			{
				if(page.isDefaultPage)
				{
					hasDefault = true;
					break;
				}
			}
			if(!hasDefault) throw new Error("Descriptors not contain default page");
			_descriptors = value;
			dispatchEvent(new Event("descriptorsChange"));
		}
				
		public function Navigator()
		{
			_redirectionStack = new Array();
			Application.application.addEventListener(FlexEvent.APPLICATION_COMPLETE, onContainerShow);
		}

		private function initBrowserManager():void
		{
			browserManager.addEventListener(BrowserChangeEvent.BROWSER_URL_CHANGE,
				onBrowserUrlChange, false, 0, true);
			browserManager.init();
			_urlManager = new UrlManager(browserManager);
		}
		
		private function onBrowserUrlChange(e:Event):void
		{
			if((_urlManager == null) || (_roleManager == null)) return;
			var rawPage:Object = _urlManager.getObjectFromUrl();
			var page:NavigationEntityDescriptor = getPageByName(rawPage[UrlManager.PAGE_QUERY_STRING]);
			tryShow(page.name, rawPage, false, false);
		}
		
		private function onContainerShow(e:Event):void
		{
			restoreBookmark();
		}
		
		private function saveRedirection(page:String, params:Object):void
		{
			_redirectionStack.push({name:page, params:params});
		}
		
		private function getCurrentRedirection():Object
		{
			return _redirectionStack.pop();
		}
		
		private function getDefaultPage():NavigationEntityDescriptor
		{
			for each(var page:NavigationEntityDescriptor in _descriptors)
			{
				if(page.isDefaultPage)
					return page;
			}
			return null;
		}
		
		private function getPageByName(name:String):NavigationEntityDescriptor
		{
			for each(var page:NavigationEntityDescriptor in _descriptors)
			{
				if(page.name == name)
					return page;
			}
			return getDefaultPage();
		}
		
		private function getLoginPage():NavigationEntityDescriptor
		{
			for each(var page:NavigationEntityDescriptor in _descriptors)
			{
				if(page.isLoginPage)
					return page;
			}
			return null;
		} 
		
		private function isAuhorize(page:NavigationEntityDescriptor):Boolean
		{
			if((page.authorizedRoles == null) || (page.authorizedRoles.length == 0)) return true;
			return _roleManager.canAccess(page.authorizedRoles);
		}
		
		//override for different type of container
		protected function navigate(page:NavigationEntityDescriptor):void
		{
			currentPage = page.name;
			if((container) && (page is PageDescriptor))
				container.selectedChild = (page as PageDescriptor).instance;
			else
				selectedPageIndex = (page as ModelDescriptor).pageIndex;
			dispatchEvent(new Event(Event.CHANGE)); 
		}
		
		private function tryShow(name:String, params:Object = null, loadInitialData:Boolean=true, writeUrl:Boolean = true):void
		{
			var page:NavigationEntityDescriptor = getPageByName(name);
			//page not found show default page
			if(page == null)
			{
				var def:NavigationEntityDescriptor = getDefaultPage();
				navigate(def);
				if(writeUrl) _urlManager.writeObjectToUrl(def.name, def.title, null);
				return;
			}
				
			if(isAuhorize(page))
			{
				//show page if user is autorized
				currentParams = params;
				isReload = false;
				page.invokeInitialFunction(params, false);
				navigate(page);
				if(writeUrl) 
				{
					_urlManager.writeObjectToUrl(page.name, page.title, params);
					if(_gaTracker) _gaTracker.trackPageview(_browserManager.fragment);
				}
				
				return;
			}
			else
			{
				//show login page if not authorized
				saveRedirection(name, params);
				var login:NavigationEntityDescriptor = getLoginPage();
				navigate(login);
				return;
			}
		}
		
		public function restoreBookmark():void
		{
			var bookmark:Object = _urlManager.getObjectFromUrl();
			if(bookmark[UrlManager.PAGE_QUERY_STRING] != null)
				tryShow(bookmark[UrlManager.PAGE_QUERY_STRING], bookmark);
			else
				tryShow(getDefaultPage().name, null, true, false);
		}
		
		public function show(name:String, params:Object = null):void
		{
			tryShow(name, params);
		}
		
		public function pushCurrentPageToRedirectionList():void
		{
			var rawPage:Object = _urlManager.getObjectFromUrl();
			saveRedirection(rawPage[UrlManager.PAGE_QUERY_STRING], rawPage);
		}
		
		public function restoreRedirection():void
		{
			var page:Object = getCurrentRedirection();
			show(page.name, page.params);
		}
		
		public function reload():void
		{
			var rawPage:Object = _urlManager.getObjectFromUrl();
			var page:NavigationEntityDescriptor = getPageByName(rawPage[UrlManager.PAGE_QUERY_STRING]);
			currentParams = rawPage;
			isReload = true;
			page.invokeInitialFunction(rawPage, true);
			dispatchEvent(new Event(Event.CHANGE));
		}
	}
}